#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#include <unistd.h>
#include <poll.h>

#include <linux/types.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <ifaddrs.h>


// ================================================================================

const int ET_IPV4  = 0x0800;

struct ArpFrame {
    uint16_t hwtype;
    uint16_t ptype;
    uint8_t  hlen;
    uint8_t  plen;
    uint16_t op;
    uint8_t  sha[6];
    uint8_t  spa[4];
    uint8_t  tha[6];
    uint8_t  tpa[4];
};

const int ARP_OP_REQUEST = 1;
const int ARP_OP_RESPONSE = 2;

const int ARP_HW_ETHERNET = 1;

const int HWADDR_LEN = 6;
const int IPADDR_LEN = 4;

static int sendArpRequest(sockaddr_in *inetAddr, sockaddr_ll *hwAddr, in_addr_t ip, int sock) {
    sockaddr_ll sockAddr;
    ArpFrame arpFrame;

    sockAddr.sll_family = AF_PACKET;
    sockAddr.sll_protocol = htons(ETH_P_ARP);
    sockAddr.sll_ifindex = hwAddr->sll_ifindex;
    sockAddr.sll_hatype = ARPHRD_ETHER;
    sockAddr.sll_pkttype = PACKET_BROADCAST;
    sockAddr.sll_halen = HWADDR_LEN;
    memset(sockAddr.sll_addr, 0xFF, HWADDR_LEN);

    arpFrame.hwtype = htons(ARP_HW_ETHERNET);
    arpFrame.ptype = htons(ET_IPV4);
    arpFrame.plen = IPADDR_LEN;
    arpFrame.hlen = HWADDR_LEN;
    arpFrame.op = htons(ARP_OP_REQUEST);
    memcpy(arpFrame.sha, hwAddr->sll_addr, HWADDR_LEN);
    memcpy(arpFrame.spa, &inetAddr->sin_addr, IPADDR_LEN);
    memset(arpFrame.tha, 0, HWADDR_LEN);
    memcpy(arpFrame.tpa, &ip, IPADDR_LEN);

    printf("Sending an ARP request\n");

    return sendto(sock, &arpFrame, sizeof(arpFrame), 0, (sockaddr*)&sockAddr, sizeof(sockAddr));
}


const int ERROR = -1;
const int RETRY = 0;
const int OK = 1;

static int timeout = 1000;

static int receiveArpResponse(int sock, in_addr_t ip) {
    struct ArpFrame frame;
    struct pollfd pfd;
    int ret;

    pfd.fd = sock;
    pfd.events = POLLIN;

    int pollRes = poll(&pfd, 1, timeout);
    if (pollRes == 0) {
        return RETRY;
    }
    else if (pollRes < 0) {
        printf("Error waiting response\n");
        return ERROR;
    }

    int size = recv(sock, &frame, sizeof(frame), 0);
    if (size == sizeof(frame)) {
        if (frame.op == htons(ARP_OP_RESPONSE) && memcmp(frame.spa, &ip, IPADDR_LEN) == 0) {
            printf("%d.%d.%d.%d has mac %02X:%02X:%02X:%02X:%02X:%02X\n",
                   frame.spa[0], frame.spa[1], frame.spa[2], frame.spa[3],
                   frame.sha[0], frame.sha[1], frame.sha[2], frame.sha[3], frame.sha[4], frame.sha[5]);
            ret = OK;
        } else {
            printf("Response from non-target received\n");
            ret = RETRY;
        }
    } else if (size > 0) {
        printf("Bad response\n");
        ret = RETRY;
    }
    return ret;
}


int main(int argc, char *argv[]) {
    ifaddrs *interface;
    in_addr targetInAddr;
    in_addr_t targetIp;
    sockaddr_in *inetAddr = 0;
    sockaddr_ll *hwAddr = 0;
    char *ifaceName = 0;

    if (argc < 2) {
        printf("Usage: Arp <ip_address>\n");
        return 1;
    }

    if (!inet_aton(argv[1], &targetInAddr)) {
        printf("Invalid IP address");
        return 1;
    }
    targetIp = targetInAddr.s_addr;

    getifaddrs(&interface);
    ifaddrs *head = interface;
    for (; interface != NULL; interface = interface->ifa_next) {
        if (interface->ifa_addr->sa_family == AF_INET) {
            in_addr_t ifIp = ((sockaddr_in *)interface->ifa_addr)->sin_addr.s_addr;
            in_addr_t ifMask = ((sockaddr_in *)interface->ifa_netmask)->sin_addr.s_addr;
            if ((ifIp & ifMask) == (targetIp & ifMask)) {
                ifaceName = interface->ifa_name;
                inetAddr = (sockaddr_in *)interface->ifa_addr;
                break;
            }
        }
    }

    if (ifaceName == NULL) {
        printf("Can't resolve ip address");
        freeifaddrs(interface);
        return 1;
    }
    for (interface = head; interface != NULL; interface = interface->ifa_next) {
        if (!strcmp(ifaceName, interface->ifa_name) && interface->ifa_addr->sa_family == AF_PACKET) {
            hwAddr = (struct sockaddr_ll*)interface->ifa_addr;
            hwAddr->sll_protocol = htons(ETH_P_ARP);
            break;
        }
    }

    if (hwAddr == NULL || inetAddr == NULL) {
        printf("Can't find required interface\n");
        freeifaddrs(interface);
        return 1;
    }

    int sock = socket(AF_PACKET, SOCK_DGRAM, ETH_P_ARP);
    if (sock == -1) {
        printf("Can't create socket\n");
        freeifaddrs(head);
        return 1;
    }

    if (bind(sock, (sockaddr*)hwAddr, sizeof(sockaddr_ll)) != 0) {
        printf("Can't bind socket\n");
        close(sock);
        freeifaddrs(head);
        return 1;
    }

    if (sendArpRequest(inetAddr, hwAddr, targetIp, sock) != sizeof(ArpFrame)) {
        printf("Request sending failed");
        return 1;
    }
    int retries = 30;

    int result = RETRY;
    while (result == RETRY && retries > 0) {
        result = receiveArpResponse(sock, targetIp);
        retries--;
    }
    if (retries == 0 && result != OK) {
        printf("Retries are exceeded\n");
    }
    close(sock);
    freeifaddrs(head);
    return 0;
}
